/*
** hc.c
**
** Help-Compiler, Help compiler utility.
** Compile with any memory model but large data (compact
** or large) provides more run-time memory.
**
** Pictor, Version 1.51, Copyright (c) 1992-94 SoftCircuits
** Redistributed by permission.
*/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#include <pictor.h>
#include <compress.h>

#ifdef __WATCOMC__
	#include <io.h> /* Location of unlink() */
	#define putw _putw
	#define unlink _unlink
#endif

#define MAX_LABEL    80
struct item {
	char label[MAX_LABEL + 1];
	long offset;
	unsigned comp_size;
	unsigned uncomp_size;
	struct item *next;
};

struct item *first = NULL;
struct item **array;
char signature[] = "PICTORHLP00";
unsigned num_topics,max_comp = 0,max_uncomp = 0;
unsigned comp_size,uncomp_size;
unsigned errors = 0,warnings = 0;
unsigned line = 0;
char *whtspc = " \t\n";
char buffer[BUFSIZ];
char *outfile;

#define WARN   0x00
#define ERROR  0x01
#define FATAL  0x02

/*
** displays diagnostic and counts error
*/
void error(char *msg,int type)
{
	static char *error_types[] = {
		"Warning",
		"Error",
		"Fatal error",
	};

	printf("%s : ",error_types[type]);
	if(line != 0) printf("Line %d : ",line);
	printf("%s\n",msg);

	if(type == WARN)
		warnings++;
	else
		errors++;

} /* error */

/*
** sets dos errorlevel to number of errors, closes all files and exits
** this function deletes the temporary file and if errors have occured,
** deletes the output file
*/
void terminate(void)
{
	fcloseall();

	if(errors) {
		printf("\nOutput file not created\n");
		unlink(outfile);
	}
	exit(errors);

} /* terminate */

/*
** compare routine for qsort()
*/
int compare(const void *elem1,const void *elem2)
{
	return(stricmp(*(char **)elem1,*(char **)elem2));

} /* compare */

/*
** builds compressed output file from input file
*/
void create_output(char *filename,FILE *tmp_stream)
{
	int len;
	unsigned i;
	FILE *stream;
	NODE *root_node;
	struct item *curr;
	void *inbuff,*outbuff;
	long fpos = 0,fpos2 = 0;

	/* compute most efficient compression code tree */
	rewind(tmp_stream);
	root_node = gettree(tmp_stream);
	if(root_node == NULL) {
		error("Insufficient memory",FATAL);
		terminate();
	}

	/* open output file */
	stream = fopen(filename,"wb");
	if(stream == NULL) {
		error("Unable to create output file",FATAL);
		terminate();
	}
	/* write signature */
	fwrite(signature,sizeof(char),sizeof(signature),stream);

	/* write code tree */
	len = writetree(root_node,(BYTE *)buffer);
	putw(len,stream);
	fwrite(buffer,sizeof(char),len,stream);

	/* make room for file offset pointer */
	fpos = ftell(stream);
	fwrite(&fpos,sizeof(long),1,stream);

	/* allocate compression buffers */
	inbuff = malloc(max_uncomp);
	outbuff = malloc(max_uncomp + 512);
	if(inbuff == NULL || outbuff == NULL) {
		error("Insufficient memory",FATAL);
		terminate();
	}

	/* write compressed help text */
	rewind(tmp_stream);
	for(i = 0,curr = first;curr != NULL;i++,curr = curr->next) {
		fread(inbuff,sizeof(char),curr->uncomp_size,tmp_stream);
		curr->comp_size = compress(inbuff,curr->uncomp_size,outbuff,root_node);
		if(curr->comp_size > max_comp)
			max_comp = curr->comp_size;
		curr->offset = ftell(stream);
		putw(curr->comp_size,stream);
		putw(curr->uncomp_size,stream);
		fwrite(outbuff,sizeof(char),curr->comp_size,stream);
	}

	fpos2 = ftell(stream);

	/* */
	putw(num_topics,stream);
	putw(max_comp,stream);
	putw(max_uncomp,stream);

	/* write sorted file offsets for each topics */
	for(i = 0;i < num_topics;i++) {
		fwrite(&(array[i]->offset),sizeof(array[0]->offset),1,stream);
	}

	/* */
	inbuff = realloc(inbuff,uncomp_size);
	outbuff = realloc(outbuff,uncomp_size + 512);
	if(inbuff == NULL || outbuff == NULL) {
		error("Insufficient memory",FATAL);
		terminate();
	}

	/* */
	fread(inbuff,sizeof(char),uncomp_size,tmp_stream);

	/* compress topic labels */
	comp_size = compress(inbuff,uncomp_size,outbuff,root_node);

	/* */
	putw(comp_size,stream);
	putw(uncomp_size,stream);

	/* write compressed topic labels to output file */
	fwrite(outbuff,sizeof(char),comp_size,stream);

	/* */
	fseek(stream,fpos,SEEK_SET);
	fwrite(&fpos2,sizeof(fpos2),1,stream);

	fclose(tmp_stream);
	fclose(stream);

} /* create_output */

/*
** parses the input file
** returns a pointer to the temporary file stream
*/
FILE *read_input(char *filename)
{
	FILE *stream,*tmp_stream;
	struct item *new,*curr = NULL;
	int in_topic = FALSE;
	char *cptr;

	/* open input file */
	if((stream = fopen(filename,"rt")) == NULL) {
		error("Unable to open input file",FATAL);
		terminate();
	}
	/* create temporary file */
	if((tmp_stream = tmpfile()) == NULL) {
		error("Unable to create temporary file",FATAL);
		terminate();
	}
	for(line = 1;fgets(buffer,BUFSIZ,stream);line++) {
		/* find first non-space character */
		cptr = (buffer + strspn(buffer,whtspc));

		if(*cptr == '@') {   /* compiler directive */
			cptr = strtok(cptr + 1,whtspc);
			if(cptr == NULL) {
				error("Compiler directive expected following '@'",ERROR);
			}
			else if(!strcmpi(cptr,"BEGIN")) {   /* new topic */
				if(!in_topic) {
					if((new = malloc(sizeof(struct item))) == NULL) {
						error("Insufficient memory",FATAL);
						terminate();
					}
					if(first == NULL)
						first = new;
					else
						curr->next = new;
					curr = new;
					curr->next = NULL;
					curr->label[0] = '\0';
					curr->uncomp_size = 0;

					cptr = strtok(NULL,"");
					if(cptr == NULL || *(cptr += strspn(cptr,whtspc)) == '\0') {
						error("@BEGIN must be followed by topic label",ERROR);
					}
					else {
						/* strip trailing whitespace */
						while(isspace(cptr[strlen(cptr) - 1]))
							cptr[strlen(cptr) - 1] = '\0';

						if(strlen(cptr) > MAX_LABEL) {
							error("Topic label too long, truncating",WARN);
							cptr[MAX_LABEL] = '\0';
						}
						strcpy(curr->label,cptr);

						/* search for duplicate labels */
						for(new = first;new->next != NULL;new = new->next) {
							if(!stricmp(cptr,new->label)) {
								error("Topic already defined",ERROR);
								break;
							}
						}
					}
					in_topic = TRUE;
				}
				else error("@BEGIN before @END",ERROR);
			}
			else if(!strcmpi(cptr,"END")) {
				if(in_topic) {
					if(curr->uncomp_size > max_uncomp)
						max_uncomp = curr->uncomp_size;
					num_topics++;
					in_topic = FALSE;

					if(strtok(NULL,whtspc) != NULL)
						error("Extra characters ignored",WARN);
				}
				else error("@END before @BEGIN",ERROR);
			}
			else error("Unknown compiler directive",ERROR);
		}
		else {
			if(in_topic) {
				fwrite(buffer,sizeof(char),strlen(buffer),tmp_stream);
				curr->uncomp_size += strlen(buffer);
			}
			else if(*cptr != '\0' && *cptr != ';')
				error("Text outside @BEGIN/@END",ERROR);
		}
	}
	if(ferror(stream)) {
		error("Error reading input file",FATAL);
		terminate();
	}
	else if(in_topic) {
		error("Unexpected end-of-file",ERROR);
		if(curr->uncomp_size > max_uncomp)
			max_uncomp = curr->uncomp_size;
		num_topics++;
	}
	fclose(stream);

	/* indicate we're no longer processing input file */
	line = 0;

	return(tmp_stream);

} /* read_input */

/*
** here's main()
*/
void main(int argc,char *argv[])
{
	unsigned i,len;
	FILE *tmp_stream;
	struct item *curr;

	printf("HC - Help Compiler, Version 1.51,"
		" Copyright (c) 1992-94 SoftCircuits\n");
	printf("Redistributed by permission.\n\n");

	if(argc != 3) {
		printf("Usage: HC <input-file> <output-file>\n");
		terminate();
	}
	outfile = argv[2];

	/* process input file */
	tmp_stream = read_input(argv[1]);

	if(num_topics == 0) {
		error("Input file contains no help topics",FATAL);
		terminate();
	}

	/* build array and sort array of pointers to items */
	if((array = malloc(sizeof(struct item *) * num_topics)) == NULL) {
		error("Insufficient memory",FATAL);
		terminate();
	}
	for(i = 0,curr = first;curr != NULL;i++,curr = curr->next) {
		array[i] = curr;
	}
	qsort(array,num_topics,sizeof(struct item *),compare);

	/* write sorted topic labels to temporary file so */
	/* they'll be included in the compression code tree */
	for(uncomp_size = 0,i = 0;i < num_topics;i++) {
		len = (strlen(array[i]->label) + 1);
		fwrite(array[i]->label,sizeof(char),len,tmp_stream);
		uncomp_size += len;
	}
	create_output(argv[2],tmp_stream);

	printf("%9d Error(s)\n",errors);
	printf("%9d Warning(s)\n",warnings);
	terminate();

} /* main */


